{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:24.748414Z",
     "start_time": "2025-03-04T01:19:17.984566Z"
    }
   },
   "source": [
    "# 导入库\n",
    "import matplotlib as mpl\n",
    "# Matplotlib 绘制的图形会直接嵌入到 Notebook 的输出单元格中，而不是弹出一个独立的窗口\n",
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "\n",
    "# os库提供了一种使用操作系统相关功能的便捷方式，允许与操作系统进行交互\n",
    "import os\n",
    "\n",
    "# sys库主要用于处理Python运行时的环境相关信息以及与解释器的交互\n",
    "import sys\n",
    "import time\n",
    "\n",
    "# tqdm库是一个快速，可扩展的Python进度条，可以在 Python 长循环中添加一个进度提示信息，用户只需要封装任意的迭代器 tqdm(iterator)，即可获得一个进度条显示迭代进度。\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "\n",
    "# torch.nn是PyTorch中用于构建神经网络的模块\n",
    "import torch.nn as nn\n",
    "\n",
    "# torch.nn.functional是PyTorch中包含了神经网络的一些常用函数\n",
    "import torch.nn.functional as F\n",
    "\n",
    "# Python 中的一个属性，用于获取当前 Python 解释器的版本信息。它返回一个命名元组（named tuple）\n",
    "print(sys.version_info)\n",
    "\n",
    "# 遍历模块，打印模块名称和版本信息，快速检查当前环境中安装的某些常用 Python 库的版本信息\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "# 判断是否有 GPU，如果有，则使用 GPU 进行训练，否则使用 CPU 进行训练\n",
    "# torch.cuda.is_available()用于判断是否有GPU\n",
    "# torch.device(\"cuda:0\")创建一个 PyTorch 设备对象，表示使用第一个 GPU（索引为 0）\n",
    "# torch.device(\"cpu\")创建一个 PyTorch 设备对象，表示使用 CPU\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.3.1+cu121\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 数据准备",
   "id": "d1b22457380ca42d"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:25.990634Z",
     "start_time": "2025-03-04T01:19:24.748414Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# Path: 用于处理文件路径。\n",
    "from pathlib import Path\n",
    "\n",
    "# 定义数据集的根目录为 ./cifar-10/\n",
    "# 使用 Path 对象处理文件路径\n",
    "DATA_DIR = Path(\"./cifar-10\")\n",
    "\n",
    "train_lables_file = DATA_DIR / \"trainLabels.csv\"  # 训练集标签csv文件\n",
    "test_csv_file = DATA_DIR / \"sampleSubmission.csv\"  #测试集模板csv文件\n",
    "train_folder = DATA_DIR / \"train\"  # 训练集图片文件夹\n",
    "test_folder = DATA_DIR / \"test\"  # 测试集图片文件夹\n",
    "\n",
    "#所有的类别\n",
    "class_names = [\n",
    "    'airplane',\n",
    "    'automobile',\n",
    "    'bird',\n",
    "    'cat',\n",
    "    'deer',\n",
    "    'dog',\n",
    "    'frog',\n",
    "    'horse',\n",
    "    'ship',\n",
    "    'truck',\n",
    "]\n",
    "\n",
    "\n",
    "# 定义一个函数 parse_csv_file，用于解析 CSV 文件并返回图像路径和标签的列表\n",
    "# filepath: CSV 文件的路径。\n",
    "# folder: 图像文件夹的路径\n",
    "# 读取 CSV 文件，解析每一行，生成图像路径和标签的元组列表\n",
    "def parse_csv_file(filepath, folder):\n",
    "    # 初始化一个空列表 results，用于存储解析结果\n",
    "    # 使用列表存储图像路径和标签的元组,方便后续访问和操作解析结果\n",
    "    results = []\n",
    "\n",
    "    # 打开 CSV 文件，读取所有行并跳过第一行（表头）\n",
    "    # 使用 open 函数打开文件，readlines() 读取所有行，[1:] 跳过表头\n",
    "    with open(filepath, 'r') as f:\n",
    "        lines = f.readlines()[1:]\n",
    "\n",
    "    # 遍历每一行数据，去除换行符并按逗号分隔，得到图像 ID 和标签\n",
    "    # 使用 strip('\\n') 去除换行符，split(',') 按逗号分隔,提取图像 ID 和标签\n",
    "    for line in lines:\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "\n",
    "        # 拼接图像文件的完整路径\n",
    "        # 使用 Path 对象拼接路径，f\"{image_id}.png\" 生成图像文件名\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "\n",
    "        # 将图像路径和标签的元组添加到 results 列表中\n",
    "        results.append((image_full_path, label_str))\n",
    "    return results\n",
    "\n",
    "\n",
    "#  调用 parse_csv_file 函数，解析训练集和测试集的 CSV 文件\n",
    "train_labels_info = parse_csv_file(train_lables_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "\n"
   ],
   "id": "8c5d1e14b941feb4",
   "outputs": [],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:26.032686Z",
     "start_time": "2025-03-04T01:19:25.991640Z"
    }
   },
   "cell_type": "code",
   "source": [
    "train_df = pd.DataFrame(train_labels_info[0:45000])  # 取前45000张图片作为训练集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])  # 取后5000张图片作为验证集\n",
    "test_df = pd.DataFrame(test_csv_info)  # 取测试集\n",
    "\n",
    "train_df.columns = ['filepath', 'class']  # 给数据框添加列名\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']  # 数据准备"
   ],
   "id": "905c39fb38ff2ad8",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:28.198043Z",
     "start_time": "2025-03-04T01:19:26.034193Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# PIL（Python Imaging Library）用于图像处理，Image 模块可以加载和操作图像\n",
    "from PIL import Image\n",
    "\n",
    "# Dataset 是 PyTorch 中用于定义数据集的基类\n",
    "# DataLoader 用于批量加载数据，支持多线程和数据打乱\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "\n",
    "# transforms 提供了图像预处理和数据增强的工具，如调整大小、旋转、归一化等\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "# 定义一个自定义数据集类，继承自 PyTorch 的 Dataset 类\n",
    "# CIFAR-10 数据集的加载方式与默认数据集不同，需要自定义\n",
    "class Cifar10Dataset(Dataset):\n",
    "    # 定义一个字典，将数据集模式（train/eval/test）映射到对应的数据框\n",
    "    # 通过 mode 参数快速获取对应的数据集\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "\n",
    "    # 创建一个字典，将类别名称映射到索引（如 \"cat\" -> 0）\n",
    "    # 使用字典推导式生成映射关系\n",
    "    # 模型需要数值标签，而数据通常以类别名称存储，因此需要转换\n",
    "    label_to_idx = {label: idx for idx, label in enumerate(class_names)}\n",
    "\n",
    "    # 创建一个字典，将索引映射回类别名称（如 0 -> \"cat\"）\n",
    "    # 与 label_to_idx 类似，但方向相反\n",
    "    # 在模型预测后，可能需要将索引转换回类别名称以便解释结果\n",
    "    idx_to_label = {idx: label for idx, label in enumerate(class_names)}\n",
    "\n",
    "    # 初始化 Cifar10Dataset 类的实例\n",
    "    # mode：指定数据集模式（train/eval/test）。\n",
    "    # transform：指定图像预处理和数据增强的方法\n",
    "    def __init__(self, mode, transform=None):\n",
    "        # 根据 mode 获取对应的数据框\n",
    "        # 使用字典的 get 方法获取值，如果 mode 不存在则返回 None\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "\n",
    "        # 检查 mode 是否有效，如果无效则抛出错误\n",
    "        # 防止因无效模式导致后续代码出错\n",
    "        if self.df is None:\n",
    "            raise ValueError(\"mode should be one of train, val, test, but got {}\".format(mode))\n",
    "\n",
    "        # 保存 transform 参数到对象属性中\n",
    "        # 在 __getitem__ 方法中会用到 transform\n",
    "        self.transform = transform\n",
    "\n",
    "    # 根据索引获取单个样本（图像和标签）\n",
    "    # Dataset 类的核心方法，定义了如何获取数据,DataLoader 会调用此方法批量加载数据\n",
    "    def __getitem__(self, index):\n",
    "        # 从数据框中获取图像路径和标签\n",
    "        # 使用 iloc 方法按索引获取数据,数据框存储了图像路径和标签，需要按索引提取\n",
    "        img_path, label = self.df.iloc[index]\n",
    "\n",
    "        # 加载图像并转换为 RGB 格式\n",
    "        # Image.open 加载图像，convert('RGB') 确保图像为三通道\n",
    "        img = Image.open(img_path).convert('RGB')\n",
    "\n",
    "        # 对图像应用预处理和数据增强\n",
    "        img = self.transform(img)\n",
    "\n",
    "        # 将类别名称转换为索引,使用 label_to_idx 字典进行转换\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "\n",
    "    def __len__(self):\n",
    "        # 返回数据框的行数，即样本数量\n",
    "        # shape[0] 获取数据框的行数\n",
    "        return self.df.shape[0]\n",
    "\n",
    "\n",
    "# 定义图像的输入大小\n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4368, 0.4268, 0.3947], [0.2464, 0.2418, 0.2358]\n",
    "\n",
    "# 定义训练数据的预处理和数据增强方法\n",
    "# 数据增强（如旋转、翻转）可以提高模型的泛化能力，避免过拟合\n",
    "# 训练时需要增加数据的多样性，同时确保输入数据符合模型的期望格式\n",
    "transforms_train = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # 将图像调整为指定大小（32x32）\n",
    "    transforms.RandomRotation(40),  # 随机旋转图像，最大角度为40度\n",
    "    transforms.RandomHorizontalFlip(),  # 随机水平翻转图像\n",
    "    transforms.ToTensor(),  # 将图像转换为PyTorch张量（Tensor），并将像素值从 [0, 255] 归一化到 [0, 1]。\n",
    "    transforms.Normalize(mean, std)  # 根据均值和标准差对图像进行归一化\n",
    "])\n",
    "\n",
    "# 验证/测试数据不需要数据增强，只需进行基本的预处理\n",
    "# 验证/测试时，需要保持数据的原始分布，以评估模型的真实性能\n",
    "transforms_eval = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # 将图像调整为指定大小（32x32）\n",
    "    transforms.ToTensor(),  # 将图像转换为PyTorch张量（Tensor），并将像素值从 [0, 255] 归一化到 [0, 1]。\n",
    "    transforms.Normalize(mean, std)  # 根据均值和标准差对图像进行归一化\n",
    "])\n",
    "\n",
    "# 创建训练数据集对象\n",
    "train_ds = Cifar10Dataset(\"train\", transforms_train)\n",
    "eval_ds = Cifar10Dataset(\"eval\", transforms_eval)"
   ],
   "id": "260b2855ca57fe7f",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:28.202076Z",
     "start_time": "2025-03-04T01:19:28.199053Z"
    }
   },
   "cell_type": "code",
   "source": [
    "batch_size = 64\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "eval_dl = DataLoader(eval_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "efd46b230d85949e",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 定义模型",
   "id": "7f690e1881aaf798"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:28.208082Z",
     "start_time": "2025-03-04T01:19:28.203082Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 ResNet 的残差块类，继承自 nn.Module\n",
    "class Resdiual(nn.Module):\n",
    "\n",
    "    # input_channels：输入通道数。\n",
    "    # # output_channels：输出通道数。\n",
    "    # use_1x1conv：是否使用1x1卷积调整输入维度（默认否）。\n",
    "    # stride：第一个卷积的步长（默认1）\n",
    "    def __init__(self, input_channels, output_channels, use_1x1conv=False, stride=1):\n",
    "        super().__init__()\n",
    "\n",
    "        # 两个3x3卷积层，构成残差块的主路径\n",
    "        # 第一个卷积步长为 stride（可能缩小特征图尺寸）第一个卷积步长为 stride（可能缩小特征图尺寸）\n",
    "        self.conv1 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=stride,\n",
    "            padding=1,\n",
    "        )\n",
    "        # 第二个卷积步长为1（保持尺寸不变）\n",
    "        self.conv2 = nn.Conv2d(\n",
    "            in_channels=output_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=1,\n",
    "            padding=1,\n",
    "        )\n",
    "\n",
    "        # 当输入与输出通道数或尺寸不匹配时，用1x1卷积调整输入（称为“投影Shortcut”）\n",
    "        # 确保残差连接的输入和输出可以相加（维度一致）\n",
    "        if use_1x1conv:\n",
    "\n",
    "            self.conv_sc = nn.Conv2d(\n",
    "                in_channels=input_channels,\n",
    "                out_channels=output_channels,\n",
    "                kernel_size=1,\n",
    "                stride=stride,\n",
    "            )\n",
    "        else:\n",
    "            self.conv_sc = None\n",
    "\n",
    "        # 对卷积输出做标准化，加速训练并缓解梯度问题\n",
    "        self.bn1 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        self.bn2 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "\n",
    "    # 通过残差连接，网络学习的是输入与输出的差异（残差），而非直接映射，缓解梯度消失\n",
    "    def forward(self, inputs):\n",
    "        flow = F.relu(self.bn1(self.conv1(inputs)))  # 卷积1 -> BN1 -> ReLU\n",
    "        flow = self.bn2(self.conv2(flow))  # 卷积2 -> BN2\n",
    "        if self.conv_sc is not None:\n",
    "            inputs = self.conv_sc(inputs)  # 调整输入的维度（如需要）\n",
    "        return F.relu(flow + inputs)  # 残差相加 -> ReLU\n"
   ],
   "id": "d082566852a373b6",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:28.213435Z",
     "start_time": "2025-03-04T01:19:28.208589Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 ResdiualBlock 的类，继承自 nn.Module\n",
    "# 用于堆叠多个残差块\n",
    "class ResdiualBlock(nn.Module):\n",
    "\n",
    "    # 初始化残差块堆叠模块\n",
    "    def __init__(self, input_channels, output_channels, num, is_first=False):\n",
    "        \"\"\"\n",
    "        input_channels：输入通道数。\n",
    "        output_channels：输出通道数。\n",
    "        num：堆叠的残差块数量。\n",
    "        is_first：是否是第一个残差块（默认 False）\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "\n",
    "        # 定义一个 nn.Sequential 容器，用于按顺序堆叠多个残差块\n",
    "        # 简化前向传播的实现\n",
    "        # nn.Sequential 可以自动按顺序执行模块，避免手动调用每个模块\n",
    "        self.model = nn.Sequential()\n",
    "\n",
    "        # 向 nn.Sequential 中添加第一个残差块\n",
    "        # 通道翻倍,图像尺寸减半\n",
    "        self.model.append(Resdiual(\n",
    "            input_channels=input_channels,  # 输入通道数。\n",
    "            output_channels=output_channels,  # 输出通道数。\n",
    "            use_1x1conv=not is_first,  # 如果 is_first=False，则使用1x1卷积调整输入维度。\n",
    "            stride=1 if is_first else 2  # 如果 is_first=True，步长为1（不降采样）；否则步长为2（降采样）\n",
    "        ))\n",
    "\n",
    "        # 向 nn.Sequential 中添加剩余的残差块\n",
    "        # 堆叠多个残差块，保持特征图尺寸和通道数不变\n",
    "        # 在第一个残差块降采样后，后续残差块不需要再降采样\n",
    "        for _ in range(1, num):\n",
    "            self.model.append(Resdiual(\n",
    "                input_channels=output_channels,  # 输入通道数。\n",
    "                output_channels=output_channels,  # 输出通道数。\n",
    "                use_1x1conv=False,  # 不使用1x1卷积调整输入维度\n",
    "                stride=1  # 不降采样\n",
    "            ))\n",
    "\n",
    "        # 定义前向传播方法，接受输入数据 inputs，并返回模型的输出\n",
    "        # 将输入数据依次通过所有残差块\n",
    "        # 实现模块的前向传播逻辑\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)"
   ],
   "id": "2c696707f395b669",
   "outputs": [],
   "execution_count": 8
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:28.220465Z",
     "start_time": "2025-03-04T01:19:28.214443Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义了一个名为 ResNetForCifar10 的类，继承自 nn.Module，用于构建一个适用于 CIFAR-10 数据集的 ResNet 模型\n",
    "class ResNetForCifar10(nn.Module):\n",
    "\n",
    "    # 定义类的初始化方法，接受两个参数：n 和 num_classes\n",
    "    # n 用于控制残差块的数量\n",
    "    # num_classes 表示分类任务的类别数\n",
    "    def __init__(self, n=3, num_classes=10):\n",
    "        super().__init__()\n",
    "\n",
    "        # 使用 nn.Sequential 定义一个顺序执行的模型\n",
    "        # nn.Sequential 将多个层按顺序组合在一起，形成一个完整的模型\n",
    "        self.model = nn.Sequential(\n",
    "\n",
    "            # 对输入图像进行卷积操作，提取特征\n",
    "            nn.Conv2d(\n",
    "                in_channels=3,\n",
    "                out_channels=16,\n",
    "                kernel_size=3,\n",
    "                stride=1,\n",
    "            ),\n",
    "\n",
    "            # 对卷积层的输出进行归一化，加速训练并提高模型稳定性\n",
    "            # 减少内部协变量偏移，使模型更容易训练\n",
    "            # 批归一化可以加速收敛，减少对初始化的敏感性，并有一定的正则化效果\n",
    "            nn.BatchNorm2d(16, momentum=0.9, eps=1e-5),\n",
    "\n",
    "            # 对批归一化后的输出进行非线性变换\n",
    "            nn.ReLU(),\n",
    "\n",
    "            # 定义一个残差块，输入通道数为 16，输出通道数为 16，残差块的数量为 2 * n，并且是第一个残差块\n",
    "            # 残差块通过跳跃连接（skip connection）来缓解深层网络中的梯度消失问题\n",
    "            # 使模型能够训练更深的网络，同时保持较好的性能\n",
    "            ResdiualBlock(input_channels=16, output_channels=16, num=2 * n, is_first=True),\n",
    "\n",
    "            # 定义第二个残差块，输入通道数为 16，输出通道数为 32，残差块的数量为 2 * n\n",
    "            # 通过增加输出通道数，提取更高级的特征\n",
    "            ResdiualBlock(input_channels=16, output_channels=32, num=2 * n),\n",
    "\n",
    "            # 定义第三个残差块，输入通道数为 32，输出通道数为 64，残差块的数量为 2 * n\n",
    "            ResdiualBlock(input_channels=32, output_channels=64, num=2 * n),\n",
    "\n",
    "            # 定义一个自适应平均池化层，输出大小为 1x1\n",
    "            # 将特征图的空间维度缩减为 1x1\n",
    "            # 将不同大小的特征图统一为固定大小，方便后续的全连接层处理\n",
    "            nn.AdaptiveAvgPool2d((1, 1)),\n",
    "            nn.Flatten(),\n",
    "            nn.Linear(in_features=64, out_features=num_classes),\n",
    "        )\n",
    "\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.kaiming_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)"
   ],
   "id": "f6864aafe3879d18",
   "outputs": [],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:28.255666Z",
     "start_time": "2025-03-04T01:19:28.221473Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#模型总参数量\n",
    "total_params = sum(p.numel() for p in ResNetForCifar10(num_classes=len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ],
   "id": "52ce43df583d69c0",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 565386\n"
     ]
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 训练",
   "id": "a197bbdd2c4a5b81"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:28.344211Z",
     "start_time": "2025-03-04T01:19:28.256672Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        preds = logits.argmax(axis=-1)  # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ],
   "id": "df8150ac28897b69",
   "outputs": [],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:34.869061Z",
     "start_time": "2025-03-04T01:19:28.410632Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "\n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\",\n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "        )\n",
    "\n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "\n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "\n",
    "        )\n",
    "\n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ],
   "id": "f77cd56d360b0ae5",
   "outputs": [],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:34.874076Z",
     "start_time": "2025-03-04T01:19:34.870071Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "\n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ],
   "id": "f2637dbc02719fb8",
   "outputs": [],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:34.878588Z",
     "start_time": "2025-03-04T01:19:34.874585Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else:\n",
    "            self.counter += 1\n",
    "\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "id": "2434c8461b0c117d",
   "outputs": [],
   "execution_count": 14
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:35.245861Z",
     "start_time": "2025-03-04T01:19:35.239679Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练\n",
    "def training(\n",
    "        model,\n",
    "        train_loader,\n",
    "        val_loader,\n",
    "        epoch,\n",
    "        loss_fct,\n",
    "        optimizer,\n",
    "        tensorboard_callback=None,\n",
    "        save_ckpt_callback=None,\n",
    "        early_stop_callback=None,\n",
    "        eval_step=500,\n",
    "):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step,\n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                        )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict"
   ],
   "id": "258ced20adaaaf82",
   "outputs": [],
   "execution_count": 15
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:19:56.342049Z",
     "start_time": "2025-03-04T01:19:55.073990Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 20\n",
    "\n",
    "model = ResNetForCifar10(num_classes=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "\n",
    "# 定义了一个名为 OptimizerWithScheduler 的类\n",
    "# 该类将优化器（SGD）和学习率调度器（MultiStepLR）封装在一起，方便管理和使用\n",
    "# \n",
    "class OptimizerWithScheduler:\n",
    "    def __init__(self, parameters, lr, momentum, weight_decay):\n",
    "        self.optimizer = torch.optim.SGD(parameters, lr=lr, momentum=momentum, weight_decay=weight_decay)  # 优化器\n",
    "\n",
    "        # 使用 torch.optim.lr_scheduler.MultiStepLR 定义一个多步学习率调度器\n",
    "        # 动态调整学习率，以加速收敛并提高模型的性能\n",
    "        self.scheduler = torch.optim.lr_scheduler.MultiStepLR(self.optimizer, milestones=[32_000, 48_000],\n",
    "                                                              gamma=0.1)  # 学习率衰减\n",
    "\n",
    "    # 定义 step 方法，用于更新模型参数和学习率\n",
    "    def step(self):\n",
    "        self.optimizer.step()\n",
    "        self.scheduler.step()\n",
    "\n",
    "    @property\n",
    "    def param_groups(self):\n",
    "        return self.optimizer.param_groups\n",
    "\n",
    "    def zero_grad(self):\n",
    "        self.optimizer.zero_grad()\n",
    "\n",
    "\n",
    "optimizer = OptimizerWithScheduler(model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "\n",
    "exp_name = \"resnet34\"\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/{exp_name}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, IMAGE_SIZE, IMAGE_SIZE])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/{exp_name}\", save_step=len(train_dl), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)"
   ],
   "id": "6bbcc8c64142c442",
   "outputs": [],
   "execution_count": 16
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_dl,\n",
    "    eval_dl,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_dl)\n",
    ")"
   ],
   "id": "1e220b1da9a70da3"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-03T08:09:17.729451Z",
     "start_time": "2025-03-03T08:09:17.545151Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ],
   "id": "e63d3b230ff39a64",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 评估模型",
   "id": "d2690cad2259d725"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-03-04T01:20:04.244851Z",
     "start_time": "2025-03-04T01:20:02.022039Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/{exp_name}/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, eval_dl, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ],
   "id": "c271aab7e9542d7d",
   "outputs": [
    {
     "ename": "RuntimeError",
     "evalue": "Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same",
     "output_type": "error",
     "traceback": [
      "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[1;31mRuntimeError\u001B[0m                              Traceback (most recent call last)",
      "Cell \u001B[1;32mIn[17], line 7\u001B[0m\n\u001B[0;32m      4\u001B[0m model\u001B[38;5;241m.\u001B[39mload_state_dict(torch\u001B[38;5;241m.\u001B[39mload(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mcheckpoints/\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mexp_name\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m/best.ckpt\u001B[39m\u001B[38;5;124m\"\u001B[39m, map_location\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mcpu\u001B[39m\u001B[38;5;124m\"\u001B[39m))\n\u001B[0;32m      6\u001B[0m model\u001B[38;5;241m.\u001B[39meval()\n\u001B[1;32m----> 7\u001B[0m loss, acc \u001B[38;5;241m=\u001B[39m \u001B[43mevaluating\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmodel\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43meval_dl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mloss_fct\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m      8\u001B[0m \u001B[38;5;28mprint\u001B[39m(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mloss:     \u001B[39m\u001B[38;5;132;01m{\u001B[39;00mloss\u001B[38;5;132;01m:\u001B[39;00m\u001B[38;5;124m.4f\u001B[39m\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;130;01m\\n\u001B[39;00m\u001B[38;5;124maccuracy: \u001B[39m\u001B[38;5;132;01m{\u001B[39;00macc\u001B[38;5;132;01m:\u001B[39;00m\u001B[38;5;124m.4f\u001B[39m\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m)\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\utils\\_contextlib.py:115\u001B[0m, in \u001B[0;36mcontext_decorator.<locals>.decorate_context\u001B[1;34m(*args, **kwargs)\u001B[0m\n\u001B[0;32m    112\u001B[0m \u001B[38;5;129m@functools\u001B[39m\u001B[38;5;241m.\u001B[39mwraps(func)\n\u001B[0;32m    113\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mdecorate_context\u001B[39m(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[0;32m    114\u001B[0m     \u001B[38;5;28;01mwith\u001B[39;00m ctx_factory():\n\u001B[1;32m--> 115\u001B[0m         \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mfunc\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "Cell \u001B[1;32mIn[11], line 13\u001B[0m, in \u001B[0;36mevaluating\u001B[1;34m(model, dataloader, loss_fct)\u001B[0m\n\u001B[0;32m     11\u001B[0m labels \u001B[38;5;241m=\u001B[39m labels\u001B[38;5;241m.\u001B[39mto(device)\n\u001B[0;32m     12\u001B[0m \u001B[38;5;66;03m# 前向计算\u001B[39;00m\n\u001B[1;32m---> 13\u001B[0m logits \u001B[38;5;241m=\u001B[39m \u001B[43mmodel\u001B[49m\u001B[43m(\u001B[49m\u001B[43mdatas\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m     14\u001B[0m loss \u001B[38;5;241m=\u001B[39m loss_fct(logits, labels)  \u001B[38;5;66;03m# 验证集损失\u001B[39;00m\n\u001B[0;32m     15\u001B[0m loss_list\u001B[38;5;241m.\u001B[39mappend(loss\u001B[38;5;241m.\u001B[39mitem())\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1532\u001B[0m, in \u001B[0;36mModule._wrapped_call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1530\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_compiled_call_impl(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)  \u001B[38;5;66;03m# type: ignore[misc]\u001B[39;00m\n\u001B[0;32m   1531\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m-> 1532\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_call_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1541\u001B[0m, in \u001B[0;36mModule._call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1536\u001B[0m \u001B[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001B[39;00m\n\u001B[0;32m   1537\u001B[0m \u001B[38;5;66;03m# this function, and just call forward.\u001B[39;00m\n\u001B[0;32m   1538\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_pre_hooks\n\u001B[0;32m   1539\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_backward_hooks\n\u001B[0;32m   1540\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_forward_pre_hooks):\n\u001B[1;32m-> 1541\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mforward_call\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1543\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m   1544\u001B[0m     result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n",
      "Cell \u001B[1;32mIn[9], line 59\u001B[0m, in \u001B[0;36mResNetForCifar10.forward\u001B[1;34m(self, inputs)\u001B[0m\n\u001B[0;32m     58\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mforward\u001B[39m(\u001B[38;5;28mself\u001B[39m, inputs):\n\u001B[1;32m---> 59\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmodel\u001B[49m\u001B[43m(\u001B[49m\u001B[43minputs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1532\u001B[0m, in \u001B[0;36mModule._wrapped_call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1530\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_compiled_call_impl(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)  \u001B[38;5;66;03m# type: ignore[misc]\u001B[39;00m\n\u001B[0;32m   1531\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m-> 1532\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_call_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1541\u001B[0m, in \u001B[0;36mModule._call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1536\u001B[0m \u001B[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001B[39;00m\n\u001B[0;32m   1537\u001B[0m \u001B[38;5;66;03m# this function, and just call forward.\u001B[39;00m\n\u001B[0;32m   1538\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_pre_hooks\n\u001B[0;32m   1539\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_backward_hooks\n\u001B[0;32m   1540\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_forward_pre_hooks):\n\u001B[1;32m-> 1541\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mforward_call\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1543\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m   1544\u001B[0m     result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\container.py:217\u001B[0m, in \u001B[0;36mSequential.forward\u001B[1;34m(self, input)\u001B[0m\n\u001B[0;32m    215\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mforward\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;28minput\u001B[39m):\n\u001B[0;32m    216\u001B[0m     \u001B[38;5;28;01mfor\u001B[39;00m module \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mself\u001B[39m:\n\u001B[1;32m--> 217\u001B[0m         \u001B[38;5;28minput\u001B[39m \u001B[38;5;241m=\u001B[39m \u001B[43mmodule\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43minput\u001B[39;49m\u001B[43m)\u001B[49m\n\u001B[0;32m    218\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28minput\u001B[39m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1532\u001B[0m, in \u001B[0;36mModule._wrapped_call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1530\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_compiled_call_impl(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs)  \u001B[38;5;66;03m# type: ignore[misc]\u001B[39;00m\n\u001B[0;32m   1531\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[1;32m-> 1532\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_call_impl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1541\u001B[0m, in \u001B[0;36mModule._call_impl\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m   1536\u001B[0m \u001B[38;5;66;03m# If we don't have any hooks, we want to skip the rest of the logic in\u001B[39;00m\n\u001B[0;32m   1537\u001B[0m \u001B[38;5;66;03m# this function, and just call forward.\u001B[39;00m\n\u001B[0;32m   1538\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m (\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_forward_pre_hooks\n\u001B[0;32m   1539\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_backward_pre_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_backward_hooks\n\u001B[0;32m   1540\u001B[0m         \u001B[38;5;129;01mor\u001B[39;00m _global_forward_hooks \u001B[38;5;129;01mor\u001B[39;00m _global_forward_pre_hooks):\n\u001B[1;32m-> 1541\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mforward_call\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[0;32m   1543\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m   1544\u001B[0m     result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\conv.py:460\u001B[0m, in \u001B[0;36mConv2d.forward\u001B[1;34m(self, input)\u001B[0m\n\u001B[0;32m    459\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mforward\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;28minput\u001B[39m: Tensor) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Tensor:\n\u001B[1;32m--> 460\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_conv_forward\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43minput\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mweight\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbias\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\torch\\nn\\modules\\conv.py:456\u001B[0m, in \u001B[0;36mConv2d._conv_forward\u001B[1;34m(self, input, weight, bias)\u001B[0m\n\u001B[0;32m    452\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mpadding_mode \u001B[38;5;241m!=\u001B[39m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mzeros\u001B[39m\u001B[38;5;124m'\u001B[39m:\n\u001B[0;32m    453\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m F\u001B[38;5;241m.\u001B[39mconv2d(F\u001B[38;5;241m.\u001B[39mpad(\u001B[38;5;28minput\u001B[39m, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_reversed_padding_repeated_twice, mode\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mpadding_mode),\n\u001B[0;32m    454\u001B[0m                     weight, bias, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mstride,\n\u001B[0;32m    455\u001B[0m                     _pair(\u001B[38;5;241m0\u001B[39m), \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mdilation, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mgroups)\n\u001B[1;32m--> 456\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mF\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mconv2d\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43minput\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mweight\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mbias\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mstride\u001B[49m\u001B[43m,\u001B[49m\n\u001B[0;32m    457\u001B[0m \u001B[43m                \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mpadding\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mdilation\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mgroups\u001B[49m\u001B[43m)\u001B[49m\n",
      "\u001B[1;31mRuntimeError\u001B[0m: Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same"
     ]
    }
   ],
   "execution_count": 17
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "7c74377c81c3fa88"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
